/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2004, 2005 StatPro Italia srl
 Copyright (C) 2007, 2012 Ferdinando Ametrano
 Copyright (C) 2007, 2009 Piter Dias

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

#include "utilities.hpp"
#include <ql/instruments/bonds/fixedratebond.hpp>
#include <ql/instruments/bonds/floatingratebond.hpp>
#include <ql/instruments/bonds/zerocouponbond.hpp>
#include <ql/time/calendars/target.hpp>
#include <ql/time/calendars/unitedstates.hpp>
#include <ql/time/calendars/unitedkingdom.hpp>
#include <ql/time/calendars/australia.hpp>
#include <ql/time/calendars/brazil.hpp>
#include <ql/time/calendars/southafrica.hpp>
#include <ql/time/calendars/nullcalendar.hpp>
#include <ql/time/daycounters/thirty360.hpp>
#include <ql/time/daycounters/actual360.hpp>
#include <ql/time/daycounters/actualactual.hpp>
#include <ql/time/daycounters/business252.hpp>
#include <ql/indexes/ibor/usdlibor.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/utilities/dataformatters.hpp>
#include <ql/time/schedule.hpp>
#include <ql/cashflows/fixedratecoupon.hpp>
#include <ql/cashflows/simplecashflow.hpp>
#include <ql/cashflows/couponpricer.hpp>
#include <ql/cashflows/cashflows.hpp>
#include <ql/pricingengines/bond/discountingbondengine.hpp>
#include <ql/pricingengines/bond/bondfunctions.hpp>

using namespace QuantLib;

using std::shared_ptr;

#define ASSERT_CLOSE(name, settlement, calculated, expected, tolerance)  \
    if (std::fabs(calculated-expected) > tolerance) { \
    FAIL("Failed to reproduce " << name << " at " << settlement \
               << "\n    calculated: " << std::setprecision(8) << calculated \
               << "\n    expected:   " << std::setprecision(8) << expected); \
    }

namespace {

    struct CommonVars {
        // common data
        Calendar calendar;
        Date today;
        Real faceAmount;

        // cleanup
        SavedSettings backup;

        // setup
        CommonVars() {
            calendar = TARGET();
            today = calendar.adjust(Date::todaysDate());
            Settings::instance().evaluationDate() = today;
            faceAmount = 1000000.0;
        }
    };

}


TEST_CASE("Bond_Yield", "[Bond]") {

    INFO("Testing consistency of bond price/yield calculation...");

    CommonVars vars;

    Real tolerance = 1.0e-7;
    Size maxEvaluations = 100;

    Integer issueMonths[] = { -24, -18, -12, -6, 0, 6, 12, 18, 24 };
    Integer lengths[] = { 3, 5, 10, 15, 20 };
    Natural settlementDays = 3;
    Real coupons[] = { 0.02, 0.05, 0.08 };
    Frequency frequencies[] = { Semiannual, Annual };
    DayCounter bondDayCount = Thirty360();
    BusinessDayConvention accrualConvention = Unadjusted;
    BusinessDayConvention paymentConvention = ModifiedFollowing;
    Real redemption = 100.0;

    Rate yields[] = { 0.03, 0.04, 0.05, 0.06, 0.07 };
    Compounding compounding[] = { Compounded, Continuous };

    for (Size i=0; i<LENGTH(issueMonths); i++) {
      for (Size j=0; j<LENGTH(lengths); j++) {
        for (Size k=0; k<LENGTH(coupons); k++) {
          for (Size l=0; l<LENGTH(frequencies); l++) {
            for (Size n=0; n<LENGTH(compounding); n++) {

              Date dated = vars.calendar.advance(vars.today,
                                                 issueMonths[i], Months);
              Date issue = dated;
              Date maturity = vars.calendar.advance(issue,
                                                    lengths[j], Years);

              Schedule sch(dated, maturity,
                           Period(frequencies[l]), vars.calendar,
                           accrualConvention, accrualConvention,
                           DateGeneration::Backward, false);

              FixedRateBond bond(settlementDays, vars.faceAmount, sch,
                                 std::vector<Rate>(1, coupons[k]),
                                 bondDayCount, paymentConvention,
                                 redemption, issue);

              for (Size m=0; m<LENGTH(yields); m++) {

                Real price = BondFunctions::cleanPrice(bond, yields[m],
                                                 bondDayCount,
                                                 compounding[n],
                                                 frequencies[l]);
                Rate calculated = BondFunctions::yield(bond, price,
                                             bondDayCount, compounding[n],
                                             frequencies[l],
                                             Date(),
                                             tolerance, maxEvaluations);

                if (std::fabs(yields[m]-calculated) > tolerance) {
                  // the difference might not matter
                  Real price2 = BondFunctions::cleanPrice(bond, calculated,
                                                    bondDayCount,
                                                    compounding[n],
                                                    frequencies[l]);
                  if (std::fabs(price-price2)/price > tolerance) {
                      FAIL("\nyield recalculation failed:"
                          "\n    issue:     " << issue <<
                          "\n    maturity:  " << maturity <<
                          "\n    coupon:    " << io::rate(coupons[k]) <<
                          "\n    frequency: " << frequencies[l] <<
                          "\n    yield:   " << io::rate(yields[m]) <<
                          (compounding[n] == Compounded ?
                                " compounded" : " continuous") <<
                          std::setprecision(7) <<
                          "\n    price:   " << price <<
                          "\n    yield': " << io::rate(calculated) <<
                          "\n    price': " << price2);
                  }
                }
              }
            }
          }
        }
      }
    }
}

TEST_CASE("Bond_AtmRate", "[Bond]") {

    INFO("Testing consistency of bond price/ATM rate calculation...");

    CommonVars vars;

    Real tolerance = 1.0e-7;

    Integer issueMonths[] = { -24, -18, -12, -6, 0, 6, 12, 18, 24 };
    Integer lengths[] = { 3, 5, 10, 15, 20 };
    Natural settlementDays = 3;
    Real coupons[] = { 0.02, 0.05, 0.08 };
    Frequency frequencies[] = { Semiannual, Annual };
    DayCounter bondDayCount = Thirty360();
    BusinessDayConvention accrualConvention = Unadjusted;
    BusinessDayConvention paymentConvention = ModifiedFollowing;
    Real redemption = 100.0;
    Handle<YieldTermStructure> disc(flatRate(vars.today,0.03,Actual360()));
    shared_ptr<PricingEngine> bondEngine = std::make_shared<DiscountingBondEngine>(disc);

    for (Size i=0; i<LENGTH(issueMonths); i++) {
      for (Size j=0; j<LENGTH(lengths); j++) {
        for (Size k=0; k<LENGTH(coupons); k++) {
          for (Size l=0; l<LENGTH(frequencies); l++) {
            Date dated = vars.calendar.advance(vars.today,
                                               issueMonths[i], Months);
            Date issue = dated;
            Date maturity = vars.calendar.advance(issue,
                                                  lengths[j], Years);

            Schedule sch(dated, maturity,
                         Period(frequencies[l]), vars.calendar,
                         accrualConvention, accrualConvention,
                         DateGeneration::Backward, false);

            FixedRateBond bond(settlementDays, vars.faceAmount, sch,
                             std::vector<Rate>(1, coupons[k]),
                             bondDayCount, paymentConvention,
                             redemption, issue);

            bond.setPricingEngine(bondEngine);
            Real price = bond.cleanPrice();
            Rate calculated = BondFunctions::atmRate(bond,
                                                     **disc,
                                                     bond.settlementDate(),
                                                     price);

            if (std::fabs(coupons[k]-calculated) > tolerance) {
                  FAIL("\natm rate recalculation failed:"
                      "\n today:           " << vars.today <<
                      "\n settlement date: " << bond.settlementDate() <<
                      "\n issue:           " << issue <<
                      "\n maturity:        " << maturity <<
                      "\n coupon:          " << io::rate(coupons[k]) <<
                      "\n frequency:       " << frequencies[l] <<
                      "\n clean price:     " << price <<
                      "\n dirty price:     " << price + bond.accruedAmount() <<
                      "\n atm rate:        " << io::rate(calculated));
            }
          }
        }
      }
    }
}

TEST_CASE("Bond_Zspread", "[Bond]") {

    INFO("Testing consistency of bond price/z-spread calculation...");

    CommonVars vars;

    Real tolerance = 1.0e-7;
    Size maxEvaluations = 100;

    Handle<YieldTermStructure> discountCurve(
                                       flatRate(vars.today,0.03,Actual360()));

    Integer issueMonths[] = { -24, -18, -12, -6, 0, 6, 12, 18, 24 };
    Integer lengths[] = { 3, 5, 10, 15, 20 };
    Natural settlementDays = 3;
    Real coupons[] = { 0.02, 0.05, 0.08 };
    Frequency frequencies[] = { Semiannual, Annual };
    DayCounter bondDayCount = Thirty360();
    BusinessDayConvention accrualConvention = Unadjusted;
    BusinessDayConvention paymentConvention = ModifiedFollowing;
    Real redemption = 100.0;

    Spread spreads[] = { -0.01, -0.005, 0.0, 0.005, 0.01 };
    Compounding compounding[] = { Compounded, Continuous };

    for (Size i=0; i<LENGTH(issueMonths); i++) {
      for (Size j=0; j<LENGTH(lengths); j++) {
        for (Size k=0; k<LENGTH(coupons); k++) {
          for (Size l=0; l<LENGTH(frequencies); l++) {
            for (Size n=0; n<LENGTH(compounding); n++) {

              Date dated = vars.calendar.advance(vars.today,
                                                 issueMonths[i], Months);
              Date issue = dated;
              Date maturity = vars.calendar.advance(issue,
                                                    lengths[j], Years);

              Schedule sch(dated, maturity,
                           Period(frequencies[l]), vars.calendar,
                           accrualConvention, accrualConvention,
                           DateGeneration::Backward, false);

              FixedRateBond bond(settlementDays, vars.faceAmount, sch,
                                 std::vector<Rate>(1, coupons[k]),
                                 bondDayCount, paymentConvention,
                                 redemption, issue);

              for (Size m=0; m<LENGTH(spreads); m++) {

                Real price = BondFunctions::cleanPrice(bond, *discountCurve,
                                                   spreads[m],
                                                   bondDayCount,
                                                   compounding[n],
                                                   frequencies[l]);
                Spread calculated = BondFunctions::zSpread(bond, price,
                                                           *discountCurve,
                                                           bondDayCount,
                                                           compounding[n],
                                                           frequencies[l],
                                                           Date(),
                                                           tolerance,
                                                           maxEvaluations);

                if (std::fabs(spreads[m]-calculated) > tolerance) {
                  // the difference might not matter
                  Real price2 = BondFunctions::cleanPrice(bond, *discountCurve,
                                                      calculated,
                                                      bondDayCount,
                                                      compounding[n],
                                                      frequencies[l]);
                  if (std::fabs(price-price2)/price > tolerance) {
                      FAIL("\nZ-spread recalculation failed:"
                          "\n    issue:     " << issue <<
                          "\n    maturity:  " << maturity <<
                          "\n    coupon:    " << io::rate(coupons[k]) <<
                          "\n    frequency: " << frequencies[l] <<
                          "\n    Z-spread:  " << io::rate(spreads[m]) <<
                          (compounding[n] == Compounded ?
                                " compounded" : " continuous") <<
                          std::setprecision(7) <<
                          "\n    price:     " << price <<
                          "\n    Z-spread': " << io::rate(calculated) <<
                          "\n    price':    " << price2);
                  }
                }
              }
            }
          }
        }
      }
    }
}



TEST_CASE("Bond_Theoretical", "[Bond]") {

    INFO("Testing theoretical bond price/yield calculation...");

    CommonVars vars;

    Real tolerance = 1.0e-7;
    Size maxEvaluations = 100;

    Size lengths[] = { 3, 5, 10, 15, 20 };
    Natural settlementDays = 3;
    Real coupons[] = { 0.02, 0.05, 0.08 };
    Frequency frequencies[] = { Semiannual, Annual };
    DayCounter bondDayCount = Actual360();
    BusinessDayConvention accrualConvention = Unadjusted;
    BusinessDayConvention paymentConvention = ModifiedFollowing;
    Real redemption = 100.0;

    Rate yields[] = { 0.03, 0.04, 0.05, 0.06, 0.07 };

    for (Size j=0; j<LENGTH(lengths); j++) {
      for (Size k=0; k<LENGTH(coupons); k++) {
        for (Size l=0; l<LENGTH(frequencies); l++) {

            Date dated = vars.today;
            Date issue = dated;
            Date maturity = vars.calendar.advance(issue, lengths[j], Years);

            shared_ptr<SimpleQuote> rate = std::make_shared<SimpleQuote>(0.0);
            Handle<YieldTermStructure> discountCurve(flatRate(vars.today,
                                                              rate,
                                                              bondDayCount));

            Schedule sch(dated, maturity,
                         Period(frequencies[l]), vars.calendar,
                         accrualConvention, accrualConvention,
                         DateGeneration::Backward, false);

            FixedRateBond bond(settlementDays, vars.faceAmount, sch,
                               std::vector<Rate>(1, coupons[k]),
                               bondDayCount, paymentConvention,
                               redemption, issue);

            shared_ptr<PricingEngine> bondEngine =
                                    std::make_shared<DiscountingBondEngine>(discountCurve);
            bond.setPricingEngine(bondEngine);

            for (Size m=0; m<LENGTH(yields); m++) {

                rate->setValue(yields[m]);

                Real price = BondFunctions::cleanPrice(bond, yields[m],
                                                 bondDayCount, Continuous,
                                                 frequencies[l]);
                Real calculatedPrice = bond.cleanPrice();

                if (std::fabs(price-calculatedPrice) > tolerance) {
                    FAIL("price calculation failed:" <<
                        "\n    issue:     " << issue <<
                        "\n    maturity:  " << maturity <<
                        "\n    coupon:    " << io::rate(coupons[k]) <<
                        "\n    frequency: " << frequencies[l] <<
                        "\n    yield:  " << io::rate(yields[m]) <<
                        std::setprecision(7) <<
                        "\n    expected:    " << price <<
                        "\n    calculated': " << calculatedPrice <<
                        "\n    error':      " << price-calculatedPrice);
                }

                Rate calculatedYield = BondFunctions::yield(bond, calculatedPrice,
                    bondDayCount, Continuous, frequencies[l],
                    bond.settlementDate(),
                    tolerance, maxEvaluations);
                if (std::fabs(yields[m]-calculatedYield) > tolerance) {
                    FAIL("yield calculation failed:" <<
                        "\n    issue:     " << issue <<
                        "\n    maturity:  " << maturity <<
                        "\n    coupon:    " << io::rate(coupons[k]) <<
                        "\n    frequency: " << frequencies[l] <<
                        "\n    yield:  " << io::rate(yields[m]) <<
                        std::setprecision(7) <<
                        "\n    price:  " << price <<
                        "\n    yield': " << io::rate(calculatedYield));
                }
            }
        }
      }
    }
}


TEST_CASE("Bond_Cached", "[Bond]") {

    INFO(
        "Testing bond price/yield calculation against cached values...");

    CommonVars vars;

    // with implicit settlement calculation:

    Date today(22, November, 2004);
    Settings::instance().evaluationDate() = today;

    Calendar bondCalendar = NullCalendar();
    DayCounter bondDayCount = ActualActual(ActualActual::ISMA);
    Natural settlementDays = 1;

    Handle<YieldTermStructure> discountCurve(flatRate(today,0.03,Actual360()));

    // actual market values from the evaluation date

    Frequency freq = Semiannual;
    Schedule sch1(Date(31, October, 2004),
                  Date(31, October, 2006), Period(freq), bondCalendar,
                  Unadjusted, Unadjusted, DateGeneration::Backward, false);

    FixedRateBond bond1(settlementDays, vars.faceAmount, sch1,
                        std::vector<Rate>(1, 0.025),
                        bondDayCount, ModifiedFollowing,
                        100.0, Date(1, November, 2004));

    shared_ptr<PricingEngine> bondEngine =
                                    std::make_shared<DiscountingBondEngine>(discountCurve);
    bond1.setPricingEngine(bondEngine);

    Real marketPrice1 = 99.203125;
    Rate marketYield1 = 0.02925;

    Schedule sch2(Date(15, November, 2004),
                  Date(15, November, 2009), Period(freq), bondCalendar,
                  Unadjusted, Unadjusted, DateGeneration::Backward, false);

    FixedRateBond bond2(settlementDays, vars.faceAmount, sch2,
                        std::vector<Rate>(1, 0.035),
                        bondDayCount, ModifiedFollowing,
                        100.0, Date(15, November, 2004));

    bond2.setPricingEngine(bondEngine);

    Real marketPrice2 = 99.6875;
    Rate marketYield2 = 0.03569;

    // calculated values

    Real cachedPrice1a = 99.204505, cachedPrice2a = 99.687192;
    Real cachedPrice1b = 98.943393, cachedPrice2b = 101.986794;
    Rate cachedYield1a = 0.029257,  cachedYield2a = 0.035689;
    Rate cachedYield1b = 0.029045,  cachedYield2b = 0.035375;
    Rate cachedYield1c = 0.030423,  cachedYield2c = 0.030432;

    // check
    Real tolerance = 1.0e-6;
    Real price, yield;

    price = BondFunctions::cleanPrice(bond1, marketYield1,
                             bondDayCount, Compounded, freq);
    if (std::fabs(price-cachedPrice1a) > tolerance) {
        FAIL("failed to reproduce cached price:"
                   << QL_FIXED
                   << "\n    calculated: " << price
                   << "\n    expected:   " << cachedPrice1a
                   << "\n    tolerance:  " << tolerance
                   << "\n    error:      " << price-cachedPrice1a);
    }

    price = bond1.cleanPrice();
    if (std::fabs(price-cachedPrice1b) > tolerance) {
        FAIL("failed to reproduce cached price:"
                   << QL_FIXED
                   << "\n    calculated: " << price
                   << "\n    expected:   " << cachedPrice1b
                   << "\n    tolerance:  " << tolerance
                   << "\n    error:      " << price-cachedPrice1b);
    }

    yield = BondFunctions::yield(bond1, marketPrice1, bondDayCount, Compounded, freq);
    if (std::fabs(yield-cachedYield1a) > tolerance) {
        FAIL("\nfailed to reproduce cached compounded yield:" <<
                   std::setprecision(4) <<
                   "\n    calculated: " << io::rate(yield) <<
                   "\n    expected:   " << io::rate(cachedYield1a) <<
                   "\n    tolerance:  " << io::rate(tolerance) <<
                   "\n    error:      " << io::rate(yield-cachedYield1a));
    }

    yield = BondFunctions::yield(bond1, marketPrice1, bondDayCount, Continuous, freq);
    if (std::fabs(yield-cachedYield1b) > tolerance) {
        FAIL("failed to reproduce cached continuous yield:"
                   << std::setprecision(4)
                   << "\n    calculated: " << io::rate(yield)
                   << "\n    expected:   " << io::rate(cachedYield1b)
                   << "\n    tolerance:  " << io::rate(tolerance)
                   << "\n    error:      " << io::rate(yield-cachedYield1b));
    }

    yield = BondFunctions::yield(bond1, bond1.cleanPrice(), bondDayCount, Continuous, freq, bond1.settlementDate());
    if (std::fabs(yield-cachedYield1c) > tolerance) {
        FAIL("failed to reproduce cached continuous yield:"
                   << std::setprecision(4)
                   << "\n    calculated: " << io::rate(yield)
                   << "\n    expected:   " << io::rate(cachedYield1c)
                   << "\n    tolerance:  " << io::rate(tolerance)
                   << "\n    error:      " << io::rate(yield-cachedYield1c));
    }


    price = BondFunctions::cleanPrice(bond2, marketYield2, bondDayCount, Compounded, freq);
    if (std::fabs(price-cachedPrice2a) > tolerance) {
        FAIL("failed to reproduce cached price:"
                   << QL_FIXED
                   << "\n    calculated: " << price
                   << "\n    expected:   " << cachedPrice2a
                   << "\n    tolerance:  " << tolerance
                   << "\n    error:      " << price-cachedPrice2a);
    }

    price = bond2.cleanPrice();
    if (std::fabs(price-cachedPrice2b) > tolerance) {
        FAIL("failed to reproduce cached price:"
                   << QL_FIXED
                   << "\n    calculated: " << price
                   << "\n    expected:   " << cachedPrice2b
                   << "\n    tolerance:  " << tolerance
                   << "\n    error:      " << price-cachedPrice2b);
    }

    yield = BondFunctions::yield(bond2, marketPrice2, bondDayCount, Compounded, freq);
    if (std::fabs(yield-cachedYield2a) > tolerance) {
        FAIL("failed to reproduce cached compounded yield:"
                   << std::setprecision(4)
                   << "\n    calculated: " << io::rate(yield)
                   << "\n    expected:   " << io::rate(cachedYield2a)
                   << "\n    tolerance:  " << tolerance
                   << "\n    error:      " << io::rate(yield-cachedYield2a));
    }

    yield = BondFunctions::yield(bond2, marketPrice2, bondDayCount, Continuous, freq);
    if (std::fabs(yield-cachedYield2b) > tolerance) {
        FAIL("failed to reproduce cached continuous yield:"
                   << std::setprecision(4)
                   << "\n    calculated: " << io::rate(yield)
                   << "\n    expected:   " << io::rate(cachedYield2b)
                   << "\n    tolerance:  " << tolerance
                   << "\n    error:      " << io::rate(yield-cachedYield2b));
    }

    yield = BondFunctions::yield(bond2, bond2.cleanPrice(), bondDayCount, Continuous, freq, bond2.settlementDate());
    if (std::fabs(yield-cachedYield2c) > tolerance) {
        FAIL("failed to reproduce cached continuous yield:"
                   << std::setprecision(4)
                   << "\n    calculated: " << io::rate(yield)
                   << "\n    expected:   " << io::rate(cachedYield2c)
                   << "\n    tolerance:  " << tolerance
                   << "\n    error:      " << io::rate(yield-cachedYield2c));
    }

    // with explicit settlement date:

    Schedule sch3(Date(30,November,2004),
                  Date(30,November,2006), Period(freq),
                  UnitedStates(UnitedStates::GovernmentBond),
                  Unadjusted, Unadjusted, DateGeneration::Backward, false);

    FixedRateBond bond3(settlementDays, vars.faceAmount, sch3,
                        std::vector<Rate>(1, 0.02875),
                        ActualActual(ActualActual::ISMA),
                        ModifiedFollowing,
                        100.0, Date(30,November,2004));

    bond3.setPricingEngine(bondEngine);

    Rate marketYield3 = 0.02997;

    Date settlementDate = Date(30,November,2004);
    Real cachedPrice3 = 99.764759;

    price = BondFunctions::cleanPrice(bond3, marketYield3,
                             bondDayCount, Compounded, freq, settlementDate);
    if (std::fabs(price-cachedPrice3) > tolerance) {
        FAIL("failed to reproduce cached price:"
                   << QL_FIXED
                   << "\n    calculated: " << price << ""
                   << "\n    expected:   " << cachedPrice3 << ""
                   << "\n    error:      " << price-cachedPrice3);
    }

    // this should give the same result since the issue date is the
    // earliest possible settlement date

    Settings::instance().evaluationDate() = Date(22,November,2004);

    price = BondFunctions::cleanPrice(bond3, marketYield3, bondDayCount, Compounded, freq);
    if (std::fabs(price-cachedPrice3) > tolerance) {
        FAIL("failed to reproduce cached price:"
                   << QL_FIXED
                   << "\n    calculated: " << price << ""
                   << "\n    expected:   " << cachedPrice3 << ""
                   << "\n    error:      " << price-cachedPrice3);
    }
}



TEST_CASE("Bond_CachedZero", "[Bond]") {

    INFO("Testing zero-coupon bond prices against cached values...");

    CommonVars vars;

    Date today(22,November,2004);
    Settings::instance().evaluationDate() = today;

    Natural settlementDays = 1;

    Handle<YieldTermStructure> discountCurve(flatRate(today,0.03,Actual360()));

    Real tolerance = 1.0e-6;

    // plain

    ZeroCouponBond bond1(settlementDays,
                         UnitedStates(UnitedStates::GovernmentBond),
                         vars.faceAmount,
                         Date(30,November,2008),
                         ModifiedFollowing,
                         100.0, Date(30,November,2004));

    shared_ptr<PricingEngine> bondEngine =
                                    std::make_shared<DiscountingBondEngine>(discountCurve);
    bond1.setPricingEngine(bondEngine);

    Real cachedPrice1 = 88.551726;

    Real price = bond1.cleanPrice();
    if (std::fabs(price-cachedPrice1) > tolerance) {
        FAIL("failed to reproduce cached price:\n"
                   << QL_FIXED
                   << "    calculated: " << price << "\n"
                   << "    expected:   " << cachedPrice1 << "\n"
                   << "    error:      " << price-cachedPrice1);
    }

    ZeroCouponBond bond2(settlementDays,
                         UnitedStates(UnitedStates::GovernmentBond),
                         vars.faceAmount,
                         Date(30,November,2007),
                         ModifiedFollowing,
                         100.0, Date(30,November,2004));

    bond2.setPricingEngine(bondEngine);

    Real cachedPrice2 = 91.278949;

    price = bond2.cleanPrice();
    if (std::fabs(price-cachedPrice2) > tolerance) {
        FAIL("failed to reproduce cached price:\n"
                   << QL_FIXED
                   << "    calculated: " << price << "\n"
                   << "    expected:   " << cachedPrice2 << "\n"
                   << "    error:      " << price-cachedPrice2);
    }

    ZeroCouponBond bond3(settlementDays,
                         UnitedStates(UnitedStates::GovernmentBond),
                         vars.faceAmount,
                         Date(30,November,2006),
                         ModifiedFollowing,
                         100.0, Date(30,November,2004));

    bond3.setPricingEngine(bondEngine);

    Real cachedPrice3 = 94.098006;

    price = bond3.cleanPrice();
    if (std::fabs(price-cachedPrice3) > tolerance) {
        FAIL("failed to reproduce cached price:\n"
                   << QL_FIXED
                   << "    calculated: " << price << "\n"
                   << "    expected:   " << cachedPrice3 << "\n"
                   << "    error:      " << price-cachedPrice3);
    }
}


TEST_CASE("Bond_CachedFixed", "[Bond]") {

    INFO("Testing fixed-coupon bond prices against cached values...");

    CommonVars vars;

    Date today(22,November,2004);
    Settings::instance().evaluationDate() = today;

    Natural settlementDays = 1;

    Handle<YieldTermStructure> discountCurve(flatRate(today,0.03,Actual360()));

    Real tolerance = 1.0e-6;

    // plain

    Schedule sch(Date(30,November,2004),
                 Date(30,November,2008), Period(Semiannual),
                 UnitedStates(UnitedStates::GovernmentBond),
                 Unadjusted, Unadjusted, DateGeneration::Backward, false);

    FixedRateBond bond1(settlementDays, vars.faceAmount, sch,
                        std::vector<Rate>(1, 0.02875),
                        ActualActual(ActualActual::ISMA),
                        ModifiedFollowing,
                        100.0, Date(30,November,2004));

    shared_ptr<PricingEngine> bondEngine =
                                    std::make_shared<DiscountingBondEngine>(discountCurve);
    bond1.setPricingEngine(bondEngine);

    Real cachedPrice1 = 99.298100;

    Real price = bond1.cleanPrice();
    if (std::fabs(price-cachedPrice1) > tolerance) {
        FAIL("failed to reproduce cached price:\n"
                   << QL_FIXED
                   << "    calculated: " << price << "\n"
                   << "    expected:   " << cachedPrice1 << "\n"
                   << "    error:      " << price-cachedPrice1);
    }

    // varying coupons

    std::vector<Rate> couponRates(4);
    couponRates[0] = 0.02875;
    couponRates[1] = 0.03;
    couponRates[2] = 0.03125;
    couponRates[3] = 0.0325;

    FixedRateBond bond2(settlementDays, vars.faceAmount, sch,
                          couponRates,
                          ActualActual(ActualActual::ISMA),
                          ModifiedFollowing,
                          100.0, Date(30,November,2004));

    bond2.setPricingEngine(bondEngine);

    Real cachedPrice2 = 100.334149;

    price = bond2.cleanPrice();
    if (std::fabs(price-cachedPrice2) > tolerance) {
        FAIL("failed to reproduce cached price:\n"
                   << QL_FIXED
                   << "    calculated: " << price << "\n"
                   << "    expected:   " << cachedPrice2 << "\n"
                   << "    error:      " << price-cachedPrice2);
    }

    // stub date

    Schedule sch3(Date(30,November,2004),
                  Date(30,March,2009), Period(Semiannual),
                  UnitedStates(UnitedStates::GovernmentBond),
                  Unadjusted, Unadjusted, DateGeneration::Backward, false,
                  Date(), Date(30,November,2008));

    FixedRateBond bond3(settlementDays, vars.faceAmount, sch3,
                          couponRates, ActualActual(ActualActual::ISMA),
                          ModifiedFollowing,
                          100.0, Date(30,November,2004));

    bond3.setPricingEngine(bondEngine);

    Real cachedPrice3 = 100.382794;

    price = bond3.cleanPrice();
    if (std::fabs(price-cachedPrice3) > tolerance) {
        FAIL("failed to reproduce cached price:\n"
                   << QL_FIXED
                   << "    calculated: " << price << "\n"
                   << "    expected:   " << cachedPrice3 << "\n"
                   << "    error:      " << price-cachedPrice3);
    }
}


TEST_CASE("Bond_CachedFloating", "[Bond]") {

    INFO("Testing floating-rate bond prices against cached values...");

    CommonVars vars;

    Date today(22,November,2004);
    Settings::instance().evaluationDate() = today;

    Natural settlementDays = 1;

    Handle<YieldTermStructure> riskFreeRate(flatRate(today,0.025,Actual360()));
    Handle<YieldTermStructure> discountCurve(flatRate(today,0.03,Actual360()));

    shared_ptr<IborIndex> index = std::make_shared<USDLibor>(6*Months, riskFreeRate);
    Natural fixingDays = 1;

    Real tolerance = 1.0e-6;

    shared_ptr<IborCouponPricer> pricer =
        std::make_shared<BlackIborCouponPricer>(Handle<OptionletVolatilityStructure>());

    // plain

    Schedule sch(Date(30,November,2004),
                 Date(30,November,2008),
                 Period(Semiannual),
                 UnitedStates(UnitedStates::GovernmentBond),
                 ModifiedFollowing, ModifiedFollowing,
                 DateGeneration::Backward, false);

    FloatingRateBond bond1(settlementDays, vars.faceAmount, sch,
                           index, ActualActual(ActualActual::ISMA),
                           ModifiedFollowing, fixingDays,
                           std::vector<Real>(), std::vector<Spread>(),
                           std::vector<Rate>(), std::vector<Rate>(),
                           false,
                           100.0, Date(30,November,2004));

    shared_ptr<PricingEngine> bondEngine =
                                     std::make_shared<DiscountingBondEngine>(riskFreeRate);
    bond1.setPricingEngine(bondEngine);

    setCouponPricer(bond1.cashflows(),pricer);

    #if defined(QL_USE_INDEXED_COUPON)
    Real cachedPrice1 = 99.874645;
    #else
    Real cachedPrice1 = 99.874646;
    #endif


    Real price = bond1.cleanPrice();
    if (std::fabs(price-cachedPrice1) > tolerance) {
        FAIL("failed to reproduce cached price:\n"
                   << QL_FIXED
                   << "    calculated: " << price << "\n"
                   << "    expected:   " << cachedPrice1 << "\n"
                   << "    error:      " << price-cachedPrice1);
    }

    // different risk-free and discount curve

    FloatingRateBond bond2(settlementDays, vars.faceAmount, sch,
                           index, ActualActual(ActualActual::ISMA),
                           ModifiedFollowing, fixingDays,
                           std::vector<Rate>(), std::vector<Spread>(),
                           std::vector<Rate>(), std::vector<Rate>(),
                           false,
                           100.0, Date(30,November,2004));

    shared_ptr<PricingEngine> bondEngine2 =
                                    std::make_shared<DiscountingBondEngine>(discountCurve);
    bond2.setPricingEngine(bondEngine2);

    setCouponPricer(bond2.cashflows(),pricer);

    #if defined(QL_USE_INDEXED_COUPON)
    Real cachedPrice2 = 97.955904;
    #else
    Real cachedPrice2 = 97.955904;
    #endif

    price = bond2.cleanPrice();
    if (std::fabs(price-cachedPrice2) > tolerance) {
        FAIL("failed to reproduce cached price:\n"
                   << QL_FIXED
                   << "    calculated: " << price << "\n"
                   << "    expected:   " << cachedPrice2 << "\n"
                   << "    error:      " << price-cachedPrice2);
    }

    // varying spread

    std::vector<Rate> spreads(4);
    spreads[0] = 0.001;
    spreads[1] = 0.0012;
    spreads[2] = 0.0014;
    spreads[3] = 0.0016;

    FloatingRateBond bond3(settlementDays, vars.faceAmount, sch,
                           index, ActualActual(ActualActual::ISMA),
                           ModifiedFollowing, fixingDays,
                           std::vector<Real>(), spreads,
                           std::vector<Rate>(), std::vector<Rate>(),
                           false,
                           100.0, Date(30,November,2004));

    bond3.setPricingEngine(bondEngine2);

    setCouponPricer(bond3.cashflows(),pricer);

    #if defined(QL_USE_INDEXED_COUPON)
    Real cachedPrice3 = 98.495458;
    #else
    Real cachedPrice3 = 98.495459;
    #endif

    price = bond3.cleanPrice();
    if (std::fabs(price-cachedPrice3) > tolerance) {
        FAIL("failed to reproduce cached price:\n"
                   << QL_FIXED
                   << "    calculated: " << price << "\n"
                   << "    expected:   " << cachedPrice3 << "\n"
                   << "    error:      " << price-cachedPrice3);
    }
}

TEST_CASE("Bond_BrazilianCached", "[Bond]") {

    INFO(
        "Testing Brazilian public bond prices against Andima cached values...");

    CommonVars vars;

    Natural settlementDays = 1;
    Real faceAmount = 1000.0;
    Real redemption = 100.0;
    Date today(6,June,2007);
    Date issueDate(1,January,2007);

    // The tolerance is high because Andima truncate yields
    Real tolerance = 1.0e-4;

    // Reset evaluation date
    Settings::instance().evaluationDate() = today;

    // NTN-F maturity dates
    std::vector<Date> maturityDates(6);
    maturityDates[0] = Date(1,January,2008);
    maturityDates[1] = Date(1,January,2010);
    maturityDates[2] = Date(1,July,2010);
    maturityDates[3] = Date(1,January,2012);
    maturityDates[4] = Date(1,January,2014);
    maturityDates[5] = Date(1,January,2017);

    // Andima NTN-F yields
    std::vector<Rate> yields(6);
    yields[0] = 0.114614;
    yields[1] = 0.105726;
    yields[2] = 0.105328;
    yields[3] = 0.104283;
    yields[4] = 0.103218;
    yields[5] = 0.102948;

    // Andima NTN-F prices
    std::vector<Rate> prices(6);
    prices[0] = 1034.63031372;
    prices[1] = 1030.09919487;
    prices[2] = 1029.98307160;
    prices[3] = 1028.13585068;
    prices[4] = 1028.33383817;
    prices[5] = 1026.19716497;

    std::vector<InterestRate> couponRates(1);
    couponRates[0] = InterestRate(0.1, Thirty360(), Compounded,Annual);

    for (Size bondIndex = 0; bondIndex < maturityDates.size(); bondIndex++) {

        InterestRate yield(yields[bondIndex],
                           Business252(Brazil()),
                           Compounded, Annual);

        Schedule schedule(Date(1,January,2007),
                          maturityDates[bondIndex], Period(Semiannual),
                          Brazil(Brazil::Settlement),
                          Unadjusted, Unadjusted,
                          DateGeneration::Backward, false);

        FixedRateBond bond(settlementDays,
            faceAmount,
            schedule,
            couponRates,
            Following,
            redemption,
            issueDate);

        Real cachedPrice = prices[bondIndex];
        Real price = faceAmount *
            (BondFunctions::cleanPrice(bond, yield.rate(), yield.dayCounter(),
                                 yield.compounding(), yield.frequency(),
                                 today) + bond.accruedAmount(today)) / 100.0;

        if (std::fabs(price-cachedPrice) > tolerance) {
            FAIL_CHECK("failed to reproduce Andima cached price:\n"
                        << QL_FIXED
                        << "    calculated: " << price << "\n"
                        << "    expected:   " << cachedPrice << "\n"
                        << "    error:      " << price-cachedPrice  << "\n"
                        );
        }
    }
}

TEST_CASE("Bond_ExCouponGilt", "[Bond]") {
    INFO(
        "Testing ex-coupon UK Gilt price against market values...");
    /* UK Gilts have an exCouponDate 7 business days before the coupon
       is due (see <http://www.dmo.gov.uk/index.aspx?page=Gilts/Gilt_Faq>).
       On the exCouponDate the bond still trades cum-coupon so we use
       6 days below and UK calendar

       Output verified with Bloomberg:

       ISIN: GB0009997999
       Issue Date: February 29th, 1996
       Interest Accrue: February 29th, 1996
       First Coupon: June 7th, 1996
       Maturity: June 7th, 2021
       coupon: 8
       period: 6M

       Settlement date: May 29th, 2013
       Test Price : 103
       Accrued : 38021.97802
       NPV : 106.8021978
       Yield : 7.495180593
       Yield->NPV : 106.8021978
       Yield->NPV->Price : 103
       Mod duration : 5.676044458
       Convexity : 0.4215314859
       PV 0.01 : 0.0606214023

       Settlement date: May 30th, 2013
       Test Price : 103
       Accrued : -1758.241758
       NPV : 102.8241758
       Yield : 7.496183543
       Yield->NPV : 102.8241758
       Yield->NPV->Price : 103
       Mod duration : 5.892816328
       Convexity : 0.4375621862
       PV 0.01 : 0.06059239822

       Settlement date: May 31st, 2013
       Test Price : 103
       Accrued : -1538.461538
       NPV : 102.8461538
       Yield : 7.495987492
       Yield->NPV : 102.8461539
       Yield->NPV->Price : 103
       Mod duration : 5.890186028
       Convexity : 0.4372394381
       PV 0.01 : 0.06057829784
    */
    struct test_case {
        Date settlementDate;
        Real testPrice;
        Real accruedAmount;
        Real NPV;
        Rate yield;
        Real duration;
        Real convexity;
    };

    Calendar calendar = UnitedKingdom();

    Natural settlementDays = 3;

    Date issueDate(29, February, 1996);
    Date startDate(29, February, 1996);
    Date firstCouponDate(07, June, 1996);
    Date maturityDate(07, June, 2021);

    Rate coupon = 0.08;

    Period tenor = 6*Months;
    Period exCouponPeriod = 6*Days;

    Compounding comp = Compounded;
    Frequency freq   = Semiannual;
    DayCounter dc = ActualActual(ActualActual::ISMA);

    FixedRateBond bond(settlementDays, 100.0,
                       Schedule(startDate, maturityDate, tenor,
                                NullCalendar(), Unadjusted, Unadjusted,
                                DateGeneration::Forward, true, firstCouponDate),
                       std::vector<Rate>(1, coupon),
                       dc, Unadjusted, 100.0,
                       issueDate, calendar, exCouponPeriod, calendar);

    const Leg& leg = bond.cashflows();

    test_case cases[] = {
        { Date(29,May,2013), 103.0,
          3.8021978, 106.8021978, 0.0749518,
          5.6760445, 42.1531486 },
        { Date(30,May,2013), 103.0,
          -0.1758242, 102.8241758, 0.0749618,
          5.8928163, 43.7562186 },
        { Date(31,May,2013), 103.0,
          -0.1538462, 102.8461538, 0.0749599,
          5.8901860, 43.7239438 }
    };

    for (Size i=0; i<LENGTH(cases); ++i) {
        Real accrued = bond.accruedAmount(cases[i].settlementDate);
        ASSERT_CLOSE("accrued amount", cases[i].settlementDate,
                     accrued, cases[i].accruedAmount, 1e-6);
        
        Real npv = cases[i].testPrice + accrued;
        ASSERT_CLOSE("NPV", cases[i].settlementDate,
                     npv, cases[i].NPV, 1e-6);

        Rate yield = CashFlows::yield(leg, npv, dc, comp, freq,
                                      false, cases[i].settlementDate);
        ASSERT_CLOSE("yield", cases[i].settlementDate,
                     yield, cases[i].yield, 1e-6);

        Time duration = CashFlows::duration(leg, yield, dc, comp, freq,
                                            Duration::Modified, false,
                                            cases[i].settlementDate);
        ASSERT_CLOSE("duration", cases[i].settlementDate,
                     duration, cases[i].duration, 1e-6);

        Real convexity = CashFlows::convexity(leg, yield, dc, comp, freq,
                                              false, cases[i].settlementDate);
        ASSERT_CLOSE("convexity", cases[i].settlementDate,
                     convexity, cases[i].convexity, 1e-6);

        Real calcnpv = CashFlows::npv(leg, yield, dc, comp, freq,
                                      false, cases[i].settlementDate);
        ASSERT_CLOSE("NPV from yield", cases[i].settlementDate,
                     calcnpv, cases[i].NPV, 1e-6);
        
        Real calcprice = calcnpv - accrued;
        ASSERT_CLOSE("price from yield", cases[i].settlementDate,
                     calcprice, cases[i].testPrice, 1e-6);
    }
}


TEST_CASE("Bond_ExCouponAustralianBond", "[Bond]") {
    INFO(
        "Testing ex-coupon Australian bond price against market values...");
    /* Australian Government Bonds have an exCouponDate 7 calendar
       days before the coupon is due.  On the exCouponDate the bond
       trades ex-coupon so we use 7 days below and NullCalendar.
       AGB accrued interest is rounded to 3dp.

       Output verified with Bloomberg:

       ISIN: AU300TB01208
       Issue Date: June 10th, 2004
       Interest Accrue: February 15th, 2004
       First Coupon: August 15th, 2004
       Maturity: February 15th, 2017
       coupon: 6
       period: 6M

       Settlement date: August 7th, 2014
       Test Price : 103
       Accrued : 28670
       NPV : 105.867
       Yield : 4.723814867
       Yield->NPV : 105.867
       Yield->NPV->Price : 103
       Mod duration : 2.262763296
       Convexity : 0.0654870275
       PV 0.01 : 0.02395519619

       Settlement date: August 8th, 2014
       Test Price : 103
       Accrued : -1160
       NPV : 102.884
       Yield : 4.72354833
       Yield->NPV : 102.884
       Yield->NPV->Price : 103
       Mod duration : 2.325360055
       Convexity : 0.06725307785
       PV 0.01 : 0.02392423439

       Settlement date: August 11th, 2014
       Test Price : 103
       Accrued : -660
       NPV : 102.934
       Yield : 4.719277687
       Yield->NPV : 102.934
       Yield->NPV->Price : 103
       Mod duration : 2.317320093
       Convexity : 0.06684074058
       PV 0.01 : 0.02385310264
    */
    struct test_case {
        Date settlementDate;
        Real testPrice;
        Real accruedAmount;
        Real NPV;
        Rate yield;
        Real duration;
        Real convexity;
    };

    Calendar calendar = Australia();

    Natural settlementDays = 3;

    Date issueDate(10, June, 2004);
    Date startDate(15, February, 2004);
    Date firstCouponDate(15, August, 2004);
    Date maturityDate(15, February, 2017);

    Rate coupon = 0.06;

    Period tenor = 6*Months;
    Period exCouponPeriod = 7*Days;

    Compounding comp = Compounded;
    Frequency freq   = Semiannual;
    DayCounter dc = ActualActual(ActualActual::ISMA);

    FixedRateBond bond(settlementDays, 100.0,
                       Schedule(startDate, maturityDate, tenor,
                                NullCalendar(), Unadjusted, Unadjusted,
                                DateGeneration::Forward, true, firstCouponDate),
                       std::vector<Rate>(1, coupon),
                       dc, Unadjusted, 100.0,
                       issueDate, calendar, exCouponPeriod, NullCalendar());

    const Leg& leg = bond.cashflows();

    test_case cases[] = {
        { Date(7,August,2014), 103.0,
          2.8670, 105.867, 0.04723,
          2.26276, 6.54870 },
        { Date(8,August,2014), 103.0,
          -0.1160, 102.884, 0.047235,
          2.32536, 6.72531 },
        { Date(11,August,2014), 103.0,
          -0.0660, 102.934, 0.04719,
          2.31732, 6.68407 }
    };

    for (Size i=0; i<LENGTH(cases); ++i) {
        Real accrued = bond.accruedAmount(cases[i].settlementDate);
        ASSERT_CLOSE("accrued amount", cases[i].settlementDate,
                     accrued, cases[i].accruedAmount, 1e-3);
        
        Real npv = cases[i].testPrice + accrued;
        ASSERT_CLOSE("NPV", cases[i].settlementDate,
                     npv, cases[i].NPV, 1e-3);

        Rate yield = CashFlows::yield(leg, npv, dc, comp, freq,
                                      false, cases[i].settlementDate);
        ASSERT_CLOSE("yield", cases[i].settlementDate,
                     yield, cases[i].yield, 1e-5);

        Time duration = CashFlows::duration(leg, yield, dc, comp, freq,
                                            Duration::Modified, false,
                                            cases[i].settlementDate);
        ASSERT_CLOSE("duration", cases[i].settlementDate,
                     duration, cases[i].duration, 1e-5);

        Real convexity = CashFlows::convexity(leg, yield, dc, comp, freq,
                                              false, cases[i].settlementDate);
        ASSERT_CLOSE("convexity", cases[i].settlementDate,
                     convexity, cases[i].convexity, 1e-4);

        Real calcnpv = CashFlows::npv(leg, yield, dc, comp, freq,
                                      false, cases[i].settlementDate);
        ASSERT_CLOSE("NPV from yield", cases[i].settlementDate,
                     calcnpv, cases[i].NPV, 1e-3);
        
        Real calcprice = calcnpv - accrued;
        ASSERT_CLOSE("price from yield", cases[i].settlementDate,
                     calcprice, cases[i].testPrice, 1e-3);
    }
}

/// <summary>
/// Test calculation of South African R2048 bond
/// This requires the use of the Schedule to be constructed
/// with a custom date vector
/// </summary>
TEST_CASE("Bond_BondFromScheduleWithDateVector", "[Bond]")
{
    INFO("Testing South African R2048 bond price using Schedule constructor with Date vector...");
    SavedSettings backup;

    //When pricing bond from Yield To Maturity, use NullCalendar()
    Calendar calendar = NullCalendar();

    Natural settlementDays = 3;

    Date issueDate(29, June, 2012);
    Date today(7, September, 2015);
    Date evaluationDate = calendar.adjust(today);
    Date settlementDate = calendar.advance(evaluationDate, settlementDays * Days);
    Settings::instance().evaluationDate() = evaluationDate;

    // For the schedule to generate correctly for Feb-28's, make maturity date on Feb 29
    Date maturityDate(29, February, 2048);

    Rate coupon = 0.0875;
    Compounding comp = Compounded;
    Frequency freq = Semiannual;
    DayCounter dc = ActualActual(ActualActual::Bond);

    // Yield as quoted in market
    InterestRate yield(0.09185, dc, comp, freq);
    
    Period tenor = 6 * Months;
    Period exCouponPeriod = 10 * Days;

    // Generate coupon dates for 31 Aug and end of Feb each year
    // For leap years, this will generate 29 Feb, but the bond
    // actually pays coupons on 28 Feb, regardsless of whether
    // it is a leap year or not. 
    Schedule schedule(issueDate, maturityDate, tenor,
        NullCalendar(), Unadjusted, Unadjusted,
        DateGeneration::Backward, true);

    // Adjust the 29 Feb's to 28 Feb
    std::vector<Date> dates;
    for (Size i = 0; i < schedule.size(); ++i) {
        Date d = schedule.date(i);
        if (d.month() == February && d.dayOfMonth() == 29)
            dates.emplace_back(Date(28, February, d.year()));
        else
            dates.emplace_back(d);
    }

    schedule = Schedule(dates, 
                        schedule.calendar(),
                        schedule.businessDayConvention(),
                        schedule.terminationDateBusinessDayConvention(),
                        schedule.tenor(),
                        schedule.rule(),
                        schedule.endOfMonth(),
                        schedule.isRegular());

    FixedRateBond bond(
        0, 
        100.0,
        schedule,
        std::vector<Rate>(1, coupon),
        dc, Following, 100.0,
        issueDate, calendar, 
        exCouponPeriod, calendar, Unadjusted, false);

    Real calculatedPrice = BondFunctions::dirtyPrice(bond, yield, settlementDate);
    Real expectedPrice = 95.75706;
    Real tolerance = 1e-5;
    if (std::fabs(calculatedPrice - expectedPrice) > tolerance) {
        FAIL("failed to reproduce R2048 dirty price"
            << QL_FIXED << std::setprecision(5)
            << "\n  expected:   " << expectedPrice
            << "\n  calculated: " << calculatedPrice);
    }
}

